-module(simple_server).
-behaviour(gen_server).
-export([start_link/1, throw_ex/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

start_link(Port) -> 
    case gen_server:start_link({local, ?MODULE}, ?MODULE, [], []) of
        {ok, Pid} ->
            start_listener(Port),
            io:fwrite("Server listening on: ~p~n", [Port]),
            {ok, Pid};
        {error, {already_started, OldPid}} ->
            {ok, OldPid};
        Error ->
            error_logger:error_report([{start_link, Error}])
    end.

init([]) -> 
    process_flag(trap_exit, true),
    io:format("~p starting~n", [?MODULE]),
    {ok, 0}.

throw_ex() ->
    erlang:error("Invalid exception").

%% --------------------------------------------------------------
%% @spec start_listener(Port) 
%% @doc starts the socket server and send a call to accept clients
%% --------------------------------------------------------------
start_listener(Port) ->
    {ok, Listener} = gen_tcp:listen(Port, [binary, {packet, line}, {reuseaddr, true}, {active, true}]),
    gen_server:call(?MODULE, {server_loop, Listener}).

%% --------------------------------------------------------------
%% @spec accept(Listener)
%% @doc Accept client and process data from client
%% It also spawns a new thread to continue accepting clients
%% --------------------------------------------------------------
accept(Listener) ->
    io:fwrite("Accepting on socket:~p at port~p~n", [Listener, inet:port(Listener)]),
    case gen_tcp:accept(Listener) of
        {ok, Socket} ->
            io:format("Socket accepted: ~p~n", [Socket]),
            spawn(fun() -> gen_server:call(?MODULE, {server_loop, Listener}) end),
            loop(Socket);
        Any ->
            io:fwrite(">>Received ~p~n on accept", [Any]),
            spawn(fun() -> gen_server:call(?MODULE, {server_loop, Listener}) end)
    end.
    
loop(Socket) ->
    receive
	    {tcp, Socket, Packet} ->
            StrPacket = binary_to_list(Packet),
            case check_exit(StrPacket) of
                true ->
                    self() ! {tcp_closed, Socket};
                false ->
                    gen_tcp:send(Socket, Packet),
                    loop(Socket)
            end;	     
	    {tcp_closed, Socket} ->
	        io:format("Client disconnected~n"),
	        gen_tcp:close(Socket)
    end.

check_exit("exit\r\n") ->
    io:fwrite("You are dead~n"),
    true;
check_exit(_Any) ->
    false.

%% --------------------------------------------------------------
%% @spec handle_call
%% @doc events callback implementation
%% --------------------------------------------------------------
handle_call({server_loop, Listener}, _From, State) ->
    io:fwrite("handling call to accept~n"),
    Pid = spawn(fun() -> accept(Listener) end),
    {reply, Pid, State}.
    
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(Info, State) -> 
    io:fwrite("Info: ~p~n", [Info]),
    {noreply, State}.

terminate(Reason, _State) ->
    io:fwrite("~p Process terminated with reason:~p~n", [?MODULE, Reason]),    
    ok.
    
code_change(_OldVsn, State, _Extra) -> {ok, State}.


